/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ifs.RnD.revisionRead;

import ifs.RnD.svnRead.DisplayRepositoryTree;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

/**
 *
 * @author amiplk
 */
public class DisplayReposiroryHistory {

    /**
     * ArrayList which contains all the Revision which created by reading the repository
     */
    public ArrayList<Revision> revisionObjects;
    /**
     * ArrayList which contains all the Revision object corresponding to tags, which created by reading the repository
     */
    /**
     * String value of the repository url
     */
    public String repoUrl;
    String recordFSirstComp;
    String recordLastComp;
    long startRevision;
    long endRevision;
    /**
     * String value of the repository root
     */
    public String repositoryRoot;
    ISVNAuthenticationManager authManager;
    boolean merged;
    /**
     * SVNRepository object created to the given repository url
     */
    public SVNRepository repository;
    Collection logEntries;
    /**
     * Revision Graph instance to draw the graph
     */
    /*
     * args parameter is used to obtain a repository location URL, a start
     * revision number, an end revision number, user's account name & password
     * to authenticate him to the server.
     */
    public RevisionGraph revisionGraph;
    DisplayRepositoryTree disRepoTree;
    String name;
    String password;


    /**
     *
     * @param url
     * @param type
     * @param dRepoTree
     */
    public DisplayReposiroryHistory(String url, String type, DisplayRepositoryTree dRepoTree) {
        this.repoUrl = url;
        this.disRepoTree = dRepoTree;
        name = disRepoTree.getUserName();
        password = disRepoTree.getPassword();
        merged = true;

        int revLastIndex = repoUrl.lastIndexOf("/");
        recordFSirstComp = repoUrl.substring(0, revLastIndex);
        recordLastComp = repoUrl.substring(revLastIndex + 1);
        revisionObjects = new ArrayList<Revision>();

        /*
         * Initializes the library (it must be done before ever using the
         * library itself)
         */
        setupLibrary();
        authManager = SVNWCUtil.createDefaultAuthenticationManager(name, password);
        SVNRepository testRepository = setUpRepository(repoUrl);
        try {
            repositoryRoot = testRepository.getRepositoryRoot(true).toString();
            testRepository = null;
            System.out.println("Repository Root URL -: "+repositoryRoot);
        } catch (SVNException ex) {
            Logger.getLogger(DisplayReposiroryHistory.class.getName()).log(Level.SEVERE, null, ex);
        }

        if (type.equalsIgnoreCase("FILE")) {
            repository = setUpRepository(recordFSirstComp);
        } else {
            repository = setUpRepository(repoUrl);
        }
        setRevisionRange(repository, startRevision, endRevision);
        logEntries = getLogEntries(repository);
        processLogEntries(logEntries, repoUrl, type);

        int urlLength = repositoryRoot.length();
        String recUrl = repoUrl.substring(urlLength);
        System.out.println("Repository URL -: " + recUrl);
        revisionGraph = new RevisionGraph(revisionObjects, this, repositoryRoot, recUrl, true);
        revisionGraph.test(revisionObjects, true, revisionGraph);

    }

    /*
     * Initializes the library to work with a repository via
     * different protocols.
     */
    private static void setupLibrary() {
        /*
         * For using over http:// and https://
         */
        DAVRepositoryFactory.setup();
        /*
         * For using over svn:// and svn+xxx://
         */
        SVNRepositoryFactoryImpl.setup();

        /*
         * For using over file:///
         */
        FSRepositoryFactory.setup();

    }

    /**
     * Creating SVNRepository corresponding to the given url
     * @param repoUrl
     * @return
     */
    public SVNRepository setUpRepository(String repoUrl) {
        try {
            /*
             * Creates an instance of SVNRepository to work with the repository.
             * All user's requests to the repository are relative to the
             * repository location used to create this SVNRepository.
             * SVNURL is a wrapper for URL strings that refer to repository locations.
             */
            SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(repoUrl));
            repository.setAuthenticationManager(authManager);
            return repository;
            //System.out.println("Repository URL   -   " + repository.getRepositoryRoot(true));
        } catch (SVNException svne) {
            /*
             * Perhaps a malformed URL is the cause of this exception.
             */
             JOptionPane.showMessageDialog(null, "Invalid Data Repository Location!", "Invalid Login !", 0);
//            System.err.println("error while creating an SVNRepository for the location '" + repoUrl + "': " + svne.getMessage());
//            System.exit(1);
            return null;
        }
    }

    /**
     * Create the data structure which contatins all the revision records to the repository
     * @param repository
     * @return Collection object which contains all revision records
     */
    public Collection getLogEntries(SVNRepository repository) {
        try {
            /*
             * Collects SVNLogEntry objects for all revisions in the range
             * defined by its start and end points [startRevision, endRevision].
             * For each revision commit information is represented by
             * SVNLogEntry.
             *
             * the 1st parameter (targetPaths - an array of path strings) is set
             * when restricting the [startRevision, endRevision] range to only
             * those revisions when the paths in targetPaths were changed.
             *
             * the 2nd parameter if non-null - is a user's Collection that will
             * be filled up with found SVNLogEntry objects; it's just another
             * way to reach the scope.
             *
             * startRevision, endRevision - to define a range of revisions you are
             * interested in; by default in this program - startRevision=0, endRevision=
             * the latest (HEAD) revision of the repository.
             *
             * the 5th parameter - a boolean flag changedPath - if true then for
             * each revision a corresponding SVNLogEntry will contain a map of
             * all paths which were changed in that revision.
             *
             * the 6th parameter - a boolean flag strictNode - if false and a
             * changed path is a copy (branch) of an existing one in the repository
             * then the history for its origin will be traversed; it means the
             * history of changes of the target URL (and all that there's in that
             * URL) will include the history of the origin path(s).
             * Otherwise if strictNode is true then the origin path history won't be
             * included.
             *
             * The return value is a Collection filled up with SVNLogEntry Objects.
             */

            Collection logEntries = repository.log(new String[]{""}, null, startRevision, endRevision, true, true);
            return logEntries;

        } catch (SVNException svne) {
             JOptionPane.showMessageDialog(null, "Invalid Data Repository!", "Invalid Access !", 0);
//            System.out.println("error while collecting log information for '" + repoUrl + "': " + svne.getMessage());
//            System.exit(1);
            return null;
        }
    }

    /**
     * Setting the range of revisions need to be read
     * @param repository
     * @param start
     * @param end
     */
    public void setRevisionRange(SVNRepository repository, long start, long end) { // reads all the revisions starting from the root

        if (start == 0 && end == 0) {
            try {
                endRevision = repository.getLatestRevision();
                startRevision = 0;
            } catch (SVNException ex) {
                              JOptionPane.showMessageDialog(null, "Invalid Data Repository!", "Invalid Access !", 0);
//                System.out.println("error while getting last revision number for '" + repoUrl + "': " + ex.getMessage());
//                System.exit(1);
            }
        } else {
            startRevision = start;
            endRevision = end;
        }
    }

    /**
     * process the revision records of a file/folder on the given data structure and filter them based on their url
     * this will filter the required entry set from the whole set of records
     * @param logEntries
     * @param url
     */
    public void processLogEntries(Collection logEntries, String url, String type) {

        for (Iterator entries = logEntries.iterator(); entries.hasNext();) {
            /*
             * gets a next SVNLogEntry
             */

            SVNLogEntry logEntry = (SVNLogEntry) entries.next();

            System.out.println();
            System.out.println("------------------------------------------");
            /*
             * gets the revision number
             */
            System.out.println("revision: " + logEntry.getRevision());
            /*
             * gets the author of the changes made in that revision
             */
//            System.out.println("author: " + logEntry.getAuthor());
//            /*
//             * gets the time moment when the changes were committed
//             */
//            System.out.println("date: " + logEntry.getDate());
//            /*
//             * gets the commit log message
//             */
//            System.out.println("log message: " + logEntry.getMessage());
            /*
             * displaying all paths that were changed in that revision; cahnged
             * path information is represented by SVNLogEntryPath.
             */

            if (logEntry.getChangedPaths().size() > 0) {
                System.out.println("changed paths:");
                /*
                 * keys are changed paths
                 */
                Set changedPathsSet = logEntry.getChangedPaths().keySet();
                ArrayList<RevRecord> pathList = new ArrayList<RevRecord>();            // create data structures to hold revision records
                ArrayList<RevRecord> copyPathList = new ArrayList<RevRecord>();

                for (Iterator changedPaths = changedPathsSet.iterator(); changedPaths.hasNext();) {

                    /*
                     * obtains a next SVNLogEntryPath
                     */
                    SVNLogEntryPath entryPath = (SVNLogEntryPath) logEntry.getChangedPaths().get(changedPaths.next());

                    /*
                     * SVNLogEntryPath.getPath returns the changed path itself;
                     *
                     * SVNLogEntryPath.getType returns a charecter describing
                     * how the path was changed ('A' - added, 'D' - deleted or
                     * 'M' - modified);
                     *
                     * If the path was copied from another one (branched) then
                     * SVNLogEntryPath.getCopyPath &
                     * SVNLogEntryPath.getCopyRevision tells where it was copied
                     * from and what revision the origin path was at.
                     */

                    if ((entryPath.getCopyPath() != null)) {
                        if (entryPath.getPath().endsWith(recordLastComp)) { // revisions with merges(rename,move,..) are added to copyPath
                            copyPathList.add(new RevRecord(entryPath.getPath(), entryPath.getType(), new RevRecord(entryPath.getCopyPath(), entryPath.getCopyRevision())));

                        }
                    } else {
                        RevRecord revRec = new RevRecord(entryPath.getPath(), entryPath.getType());
                        pathList.add(revRec);               // revisions without merges are added to revRec
                    }

                }

                if (type.equalsIgnoreCase("FILE")) {
                    processFileRevisions(pathList, copyPathList, logEntry, url, type);
                } else {
                    processFolderRevisions(pathList, copyPathList, logEntry, url, type);
                }
            }
        }
    }

    /**
     * Process the filtered ArrayLists of file records
     * with this method we check the given url with the other revision records to check its changes
     * @param pathList
     * @param copyPathList
     * @param logEntry
     * @param url
     */
    public void processFileRevisions(ArrayList<RevRecord> pathList, ArrayList<RevRecord> copyPathList, SVNLogEntry logEntry, String url, String type) {
        boolean contain = false;
        if (!copyPathList.isEmpty()) {// ==>contains merges
            Iterator copyPathIterator = copyPathList.iterator();

            while (copyPathIterator.hasNext()) {
                RevRecord tempPathRec = ((RevRecord) copyPathIterator.next());
                checkCopyPathList(pathList, tempPathRec, logEntry, url, type);
            }
            contain = true;
        }

        if (!contain && !pathList.isEmpty()) {
            Iterator pathIterator = pathList.iterator();

            while (pathIterator.hasNext()) {
                RevRecord tempPath = ((RevRecord) pathIterator.next());
                checkFileRecordPaths(tempPath, logEntry, url);
            }
            return;
        }
    }

    /**
     * Process the filtered ArrayLists of folder records
     * with this method we check the given url with the other revision records to check its changes
     * @param pathList
     * @param logEntry
     * @param url
     */
    public void processFolderRevisions(ArrayList<RevRecord> pathList, ArrayList<RevRecord> copyPathList, SVNLogEntry logEntry, String url, String type) {
        boolean contain = false;
        if (!copyPathList.isEmpty()) {// ==>contains merges
            Iterator copyPathIterator = copyPathList.iterator();

            while (copyPathIterator.hasNext()) {
                RevRecord tempPathRec = ((RevRecord) copyPathIterator.next());
                checkCopyPathList(pathList, tempPathRec, logEntry, url, type);
            }
            contain = true;
        }

        if (!contain && !pathList.isEmpty()) {
            Iterator pathIterator = pathList.iterator();

            while (pathIterator.hasNext()) {
                RevRecord tempPath = ((RevRecord) pathIterator.next());
                checkFolderRecordPaths(tempPath, logEntry, url);
            }
        }
    }

    /**
     * Check the log entries to get the revision change of that record
     * @param pathRev
     * @param logEntry
     * @param url
     */
    public void checkFileRecordPaths(RevRecord pathRev, SVNLogEntry logEntry, String url) {
        if (url.endsWith(pathRev.getRecordUrl())) {
            if (pathRev.getOperation() == 'A') {
                System.out.println("Added - " + pathRev.getRecordUrl());
                revisionObjects.add(new Revision(logEntry.getRevision(), logEntry.getAuthor(), logEntry.getDate(), logEntry.getMessage(), "Added", pathRev));
            } else if (pathRev.getOperation() == 'D') {
                System.out.println("Deleted - " + pathRev.getRecordUrl());
                revisionObjects.add(new Revision(logEntry.getRevision(), logEntry.getAuthor(), logEntry.getDate(), logEntry.getMessage(), "Deleted", pathRev));
            } else {
                System.out.println("Modified - " + pathRev.getRecordUrl());
                revisionObjects.add(new Revision(logEntry.getRevision(), logEntry.getAuthor(), logEntry.getDate(), logEntry.getMessage(), "Modified", pathRev));
            }
        }
    }

    /**
     * Check about merged and replace revisions and their sub components to get 'copy path' and other detils =
     * @param pathList
     * @param pathRev
     * @param logEntry
     * @param url
     */
    public void checkCopyPathList(ArrayList<RevRecord> pathList, RevRecord pathRev, SVNLogEntry logEntry, String url, String type) {

        String copyPathUrl = pathRev.getCopyComponent().getRecordUrl();         // gives the url of the merge file
        String recordUrl = pathRev.getRecordUrl();                              // url of the checking revision

        int recordLastIndex = recordUrl.lastIndexOf("/");
        String recordFirstComp = recordUrl.substring(0, recordLastIndex);
        String recordLastComp = recordUrl.substring(recordLastIndex + 1);
        int copyPathLastIndex = copyPathUrl.lastIndexOf("/");
        String copyPathFirstComp = copyPathUrl.substring(0, copyPathLastIndex);
        String copyPathLastComp = copyPathUrl.substring(copyPathLastIndex + 1);

        if (pathRev.getOperation() == 'R') {               // if the change type is 'Replace' this should handle that
            System.out.println("Replaced from - " + copyPathUrl + "  -  " + pathRev.getCopyComponent().getRevisionNO());
            if (pathRev.getCopyComponent().getRecordUrl() != null && merged) {
                merged = false;
                System.out.println(Long.parseLong(pathRev.getCopyComponent().getRevisionNO()));
                // readMerges(repositoryRoot + pathRev.getCopyComponent().getRecordUrl(), startRevision, Long.parseLong(pathRev.getCopyComponent().getRevisionNO()), type);
            }
            revisionObjects.add(new Revision(logEntry.getRevision(), logEntry.getAuthor(), logEntry.getDate(), logEntry.getMessage(), "Replace", pathRev));
            merged = true;
            return;
        } else if (url.endsWith(recordUrl)) {                                               // filter the merge of that relevent file
            System.out.println("Merged from - " + copyPathUrl + "  -  " + pathRev.getCopyComponent().getRevisionNO());
            if (pathRev.getCopyComponent().getRecordUrl() != null && merged) {
                merged = false;
                System.out.println(Long.parseLong(pathRev.getCopyComponent().getRevisionNO()));
                readMerges(repositoryRoot + pathRev.getCopyComponent().getRecordUrl(), startRevision, Long.parseLong(pathRev.getCopyComponent().getRevisionNO()), type);
            }
            revisionObjects.add(new Revision(logEntry.getRevision(), logEntry.getAuthor(), logEntry.getDate(), logEntry.getMessage(), "Merge", pathRev));
            merged = true;
            return;
        }
    }

    /**
     * Read the previous revisions(copy from path) components from the revision records
     * @param url
     * @param start
     * @param end
     */
    public void readMerges(String url, long start, long end, String type) {
        System.out.println("Merge url     -  " + url);
        int revLastIndex = url.lastIndexOf("/");
        String FSirstComp = url.substring(0, revLastIndex);
        repository = setUpRepository(FSirstComp);              //setting up the repository to previous URL level
        setRevisionRange(repository, start, end);              //setting the reviosn range
        logEntries = getLogEntries(repository);                //getting the records correspond to previous level
        processLogEntries(logEntries, url, type);              //process the url's
    }

    /**
     * Check the log entries to get the revision change of that record
     * @param pathRev
     * @param logEntry
     * @param url
     */
    public void checkFolderRecordPaths(RevRecord pathRev, SVNLogEntry logEntry, String url) {
        if (url.endsWith(pathRev.getRecordUrl())) {
            if (pathRev.getOperation() == 'A') {
                System.out.println("Added - " + pathRev.getRecordUrl());
                revisionObjects.add(new Revision(logEntry.getRevision(), logEntry.getAuthor(), logEntry.getDate(), logEntry.getMessage(), "Added", pathRev));
                // adding revision object
            } else {
                System.out.println("Modified - " + pathRev.getRecordUrl());
                revisionObjects.add(new Revision(logEntry.getRevision(), logEntry.getAuthor(), logEntry.getDate(), logEntry.getMessage(), "Modified", pathRev));
                // adding revision object
            }
        }

    }
}
